/**
 * @Author       : Hejh(3010733382@qq.com)
 * @Version      : V1.0
 * @Date         : 2024-03-21 16:12:29
 * @Description  : 物理文件相关的接口验证器
 */

const Joi = require('joi')
const { IS_UNSAFE_INTEGER } = require('@/constants/joiSchema')
const {
  ERROR_SOURCE_CODE,
  ERROR_SYSTEM_CODE
} = require('@/constants/errorCode')

const { formatFilename, convertFromBytes } = require('@/utils/format')
const { getFileInfo, combine } = require('@/utils/upload')
const R = require('@/models/Response')
const WpFile = require('@/models/fileModel')
const { Source, User, UserDepartment, Department } = require('@/models/model')
const { getFileTypeBySuffix } = require('@/utils/fileType')
const { StorageOrigin } = require('@/framework/mongoose')
const { checkParentId } = require('./sourceValidate')
const { generatId } = require('@/utils/idUtil')
const Mysequelize = require('@/models/index')
const { dumpCheck } = require('@/utils/dumpCheck')
const { SEPARATE_CHAR } = require('@/constants/index')
const UserOption = require('@/models/userOptionModel')
const { LOG_TYPE } = require('@/constants/log')
const { logRecord } = require('@/utils/logRecord')

const fileSchema = {
  filename: Joi.string().min(2).max(255),
  fileId: Joi.string(),
  size: IS_UNSAFE_INTEGER,
  chunkIds: Joi.array().items(Joi.string()),
  parentId: IS_UNSAFE_INTEGER,
  file: Joi.any(),
  chunkId: Joi.string()
}

/**
 * 看文件是否已经存在一份了
 * @param {*} ctx
 * @param {*} next
 */
module.exports.fileExistValidate = async (ctx, next) => {
  const schema = Joi.object({
    filename: fileSchema.filename.required(),
    fileId: fileSchema.fileId.required(),
    size: fileSchema.size.required(),
    chunkIds: fileSchema.chunkIds.required(),
    parentId: fileSchema.parentId.required()
  }).validate(ctx.request.body)

  if (schema.error) {
    console.log(schema.error)
    ctx.body = R.error(ERROR_SYSTEM_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  const { fileId, parentId, size } = ctx.request.body

  // 验证parentId是否正常
  // dumpCheck.check
  const b1 = await checkParentId(ctx, parentId)
  if (!b1) {
    ctx.body = R.error(ERROR_SOURCE_CODE.PARENT_ID_ERROR)
    return
  }

  const pSource = await Source.findByPk(BigInt(parentId), {
    attributes: ['id', 'filename', 'targetType', 'targetId', 'parentLevel']
  })
  // 验证空间容量是否满足
  // 根据是用户空间还是部门空间更新部门容量
  if (+pSource.targetType === 1) {
    // 获取用户
    const user = await User.findByPk(BigInt(pSource.targetId))
    if (BigInt(user.sizeMax) !== 0n) {
      if (BigInt(size) + BigInt(user.sizeUse) > BigInt(user.sizeMax)) {
        ctx.body = R.error(ERROR_SOURCE_CODE.FILE_SIZE_MAX_ERROR)
        return
      }
    }
  } else if (+pSource.targetType === 2) {
    // 获取用户-部门映射
    const userDep = await UserDepartment.findOne({
      where: {
        userId: BigInt(ctx.user.id),
        departmentId: BigInt(pSource.targetId)
      }
    })

    if (userDep && BigInt(userDep.sizeMax) !== 0n) {
      if (BigInt(size) + BigInt(userDep.sizeUse) > BigInt(userDep.sizeMax)) {
        ctx.body = R.error(ERROR_SOURCE_CODE.FILE_SIZE_MAX_ERROR)
        return
      }
    }

    // 部门本身也不能超过
    const dep = await Department.findByPk(BigInt(pSource.targetId))
    if (BigInt(dep.sizeMax) !== 0n) {
      if (BigInt(size) + BigInt(dep.sizeUse) > BigInt(dep.sizeMax)) {
        ctx.body = R.error(ERROR_SOURCE_CODE.FILE_SIZE_MAX_ERROR)
        return
      }
    }
  }

  // 文件已经存在的处理
  const one = await WpFile.findOne({
    where: { identifier: fileId }
  })

  if (one) {
    // 已经存在，创建一个source记录，链接到file
    // 根据文件夹给filename归类
    const { filename, parentId } = ctx.request.body
    const type = getFileTypeBySuffix(filename).type

    const t = await Mysequelize.transaction()

    // 查询当前目录中是否存在同名文件
    const b2 = await dumpCheck.checkFilename(ctx, parentId, filename)
    if (!b2) {
      const one = await UserOption.findOne({
        where: {
          key: 'renameFormat',
          userId: ctx.user.id
        }
      })
      // 重复了，解决冲突，使用文件夹递增的方式解决，根据用户配置来进行重名文件的处理
      if (!one) {
        ctx.request.body.filename = formatFilename(filename)
      } else {
        if (+one.value === 1) {
          ctx.request.body.filename = await formatFilename(filename, true)(
            parentId,
            filename
          )
        }
      }
    }

    try {
      const id = generatId()
      const pSource = await Source.findByPk(BigInt(parentId), {
        attributes: ['id', 'filename', 'targetType', 'targetId', 'parentLevel']
      })

      await Source.create(
        {
          id,
          targetType: pSource.targetType,
          targetId: pSource.targetId,
          filename: ctx.request.body.filename,
          parentId: parentId,
          isDir: 0,
          fileId: one.id,
          type,
          sizeDesc: convertFromBytes(one.size),
          deleteFlag: 0,
          parentLevel: pSource.parentLevel + SEPARATE_CHAR + parentId,
          createUser: ctx.user.id,
          updateUser: ctx.user.id
        },
        {
          transaction: t
        }
      )

      // 更新空间容量
      if (+pSource.targetType === 1) {
        // 获取用户
        const user = await User.findByPk(BigInt(pSource.targetId))
        await User.update(
          {
            sizeUse: BigInt(size) + BigInt(user.sizeUse)
          },
          {
            where: {
              id: BigInt(ctx.user.id)
            },
            transaction: t
          }
        )
      } else if (+pSource.targetType === 2) {
        // 获取用户-部门映射
        const userDep = await UserDepartment.findOne({
          where: {
            userId: BigInt(ctx.user.id),
            departmentId: BigInt(pSource.targetId)
          }
        })

        // 部门本身也不能超过
        const dep = await Department.findByPk(BigInt(pSource.targetId))

        if (userDep) {
          // 更新容量信息
          await UserDepartment.update(
            {
              sizeUse: BigInt(size) + BigInt(userDep.sizeUse)
            },
            {
              where: {
                id: BigInt(userDep.id)
              },
              transaction: t
            }
          )
        }

        await Department.update(
          {
            sizeUse: BigInt(size) + BigInt(dep.sizeUse)
          },
          {
            where: {
              id: BigInt(dep.id)
            },
            transaction: t
          }
        )
      }

      // 创建上传记录
      // 进行日志记录，记录文件上传
      const logs = [
        {
          type: LOG_TYPE.SOURCE_UPLOAD,
          pSource: pSource.toJSON(),
          curSource: {
            id,
            filename: ctx.request.body.filename,
            isDir: 0,
            parentId: pSource.id
          },
          sourceIds: [pSource.id, id].join(SEPARATE_CHAR)
        }
      ]
      for (let i = 0; i < logs.length; i++) {
        console.log(logs[i])
        await logRecord(ctx, logs[i].type, logs[i])
      }
      await t.commit()
      ctx.body = R.success({
        needs: []
      })
      return
      // TODO,要清除掉可能已经上传的分片文件和临时文件
    } catch (err) {
      console.log(err)
      await t.rollback()
      ctx.body = R.error(ERROR_SOURCE_CODE.UPLOAD_FILE_ERROR)
      return
    }
  }

  // 不存在，说明没有相同的文件，继续往下走
  await next()
}

module.exports.createFile = async (ctx, info, userId, t) => {
  // 3. 创建一条真实文件记录
  const id = generatId()
  const suffix = info.filename.slice(
    ((info.filename.lastIndexOf('.') - 1) >>> 0) + 2
  )

  // 获取默认的存储源
  const storage = await StorageOrigin.findOne({
    isDefault: 1
  })

  await WpFile.create(
    {
      id,
      filename: info.filename,
      path: '/' + info.fileId + info.filename,
      size: info.size,
      suffix,
      identifier: info.fileId,
      storageOriginId: storage._id.toString(),
      createUser: userId,
      updateUser: userId
    },
    {
      transaction: t
    }
  )

  // 同时创建一条source记录
  const sourceId = generatId()
  const pSource = await Source.findByPk(BigInt(info.parentId), {
    attributes: ['id', 'filename', 'targetType', 'targetId', 'parentLevel']
  })

  // 根据是用户空间还是部门空间更新部门容量
  if (+pSource.targetType === 1) {
    // 获取用户
    const user = await User.findByPk(BigInt(pSource.targetId))
    await User.update(
      {
        sizeUse: BigInt(info.size) + BigInt(user.sizeUse)
      },
      {
        where: {
          id: BigInt(userId)
        },
        transaction: t
      }
    )
  } else if (+pSource.targetType === 2) {
    // 获取用户-部门映射
    const userDep = await UserDepartment.findOne({
      where: {
        userId: BigInt(userId),
        departmentId: BigInt(pSource.targetId)
      }
    })
    // 部门本身也不能超过
    const dep = await Department.findByPk(BigInt(pSource.targetId))
    // 更新容量信息
    if (userDep) {
      await UserDepartment.update(
        {
          sizeUse: BigInt(info.size) + BigInt(userDep.sizeUse)
        },
        {
          where: {
            id: BigInt(userDep.id)
          },
          transaction: t
        }
      )
    }

    await Department.update(
      {
        sizeUse: BigInt(info.size) + BigInt(dep.sizeUse)
      },
      {
        where: {
          id: BigInt(dep.id)
        },
        transaction: t
      }
    )
  }

  await Source.create(
    {
      id: sourceId,
      targetType: pSource.targetType,
      targetId: pSource.targetId,
      filename: info.filename,
      parentId: info.parentId,
      isDir: 0,
      fileId: id,
      type: getFileTypeBySuffix(info.filename).type,
      sizeDesc: convertFromBytes(info.size),
      deleteFlag: 0,
      parentLevel: pSource.parentLevel + SEPARATE_CHAR + info.parentId,
      createUser: userId,
      updateUser: userId
    },
    {
      transaction: t
    }
  )

  // 进行日志记录，记录文件上传
  const logs = [
    {
      type: LOG_TYPE.SOURCE_UPLOAD,
      pSource: pSource.toJSON(),
      curSource: {
        id: sourceId,
        filename: info.filename,
        isDir: 0,
        parentId: pSource.id
      },
      sourceIds: [pSource.id, sourceId].join(SEPARATE_CHAR)
    }
  ]
  for (let i = 0; i < logs.length; i++) {
    console.log(logs[i])
    await logRecord(ctx, logs[i].type, logs[i])
  }
}

/**
 * @description 文件信息协商接口的参数验证
 * @param {*} ctx
 * @param {*} next
 * @returns
 */
module.exports.fileSharkValidate = async (ctx, next) => {
  const { fileId } = ctx.request.body
  // 看是否存储fileId对应的分片信息
  const info = await getFileInfo(fileId)

  // 说明已经传递了部分分片或者已经协商过了
  if (info) {
    if (!info.needs.length) {
      // 全部传完了
      const t = await Mysequelize.transaction()
      try {
        await combine(info)
        await this.createFile(ctx, info, ctx.user.id, t)
        await t.commit()
        ctx.body = R.success({
          needs: info.needs
        })
        return
      } catch {
        await t.rollback()
        ctx.body = R.error(ERROR_SOURCE_CODE.FILE_COMBINE_ERROR)
      }
    }
    ctx.body = R.success({
      needs: info.needs
    })
    return
  }
  await next()
}

/**
 * @description 文件分片上传接口的参数验证
 * @param {*} ctx
 * @param {*} next
 * @returns
 */
module.exports.chunkUploadValidate = async (ctx, next) => {
  const schema = Joi.object({
    fileId: fileSchema.fileId.required(),
    chunkId: fileSchema.chunkId.required()
  }).validate(ctx.request.body)

  if (schema.error) {
    ctx.body = R.error(ERROR_SYSTEM_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  const { fileId, chunkId } = ctx.request.body

  // 这个接口单独调用时，需要确保已经进行了文件协商
  let fileInfo = await getFileInfo(fileId)
  if (!fileInfo) {
    // 可能这个时候文件已经上传完成，可以看看fileId对应的hash是否存在
    ctx.body = R.error(ERROR_SOURCE_CODE.FILE_CHUNK_INFO_NO_ERROR)
    return
  }
  // 文件分片需属于本fileId
  if (!fileInfo.chunkIds.includes(chunkId)) {
    ctx.body = R.error(ERROR_SOURCE_CODE.FILE_CHUNK_ERROR)
    return
  }
  if (!fileInfo.needs.includes(chunkId)) {
    // 此分片已经上传,分片重复上传
    ctx.body = R.success({
      needs: fileInfo.needs
    })
    return
  }
  await next()
}
